package ftp;
import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.*;

/**
 * 保存一次的文件传输连接信息。
 * 可以通过此类方便地监测文件传输的各种过程。
 * 代码示例请参考ftp.CoreFtp.upload()
 * @author 廖龙@CS0602
 */
public class IOCell extends Thread{
    /**本地文件路径*/
    String local = "";
    /**远端文件路径*/
    String remote= "";
    /**偏移量*/
    long originalOffset = 0;
    long nowOffset = 0;
    long targetOffset = 0;
    /**数据socket*/
    Socket dataSocket = null;
    /**传输类型*/
    int transportType = -1;
    /**线程指针*/
    private Thread myThread = null;

    /**数据输入输出流*/
    DataInputStream input = null;
    DataOutputStream output = null;
    /**对应的指令流控制对象*/
    CoreFtp ftp = null;
    /**标示传输是否成功结束*/
    boolean finished = false;
    boolean error = false;
    boolean starting = false;
    /**自动重连的次数*/
    int tryCount = 1;

    /**用于显示调试信息*/
    public boolean debug = false;

    public IOCell(CoreFtp coreFtp){
	ftp = coreFtp;
    }

    public void start(){
	if (myThread == null){
	    String name;
	    if (transportType == 0)
    		name = "download ";
	    else
    		name = "upload ";
            title = new File(remote).getName();
	    myThread = new Thread(this, title);
	    myThread.start();
	}
    }


    /**
     * 线程的‘运行’函数
     */
    public void run() {
	Thread localThread = Thread.currentThread();
	while (localThread == myThread){
            System.out.println("IOCell.run().");
		;
	    try{
            if (transportType == -1 || finished || tryCount < 1)
                continue;
            System.out.println("do IO .");
    		connect();
    		transport();
	    }catch(IOException e){
		System.out.println("IO Exception: " + e.getMessage());
	    }finally{
		try{
    		    sleep(1000);
     		}catch(InterruptedException e){
		}
	    }
	}
    }

    /**
     * 设置文件传输的类别为‘下载’。
     * 数据偏移量会从fromOffset开始，根据读取到的数据长度逐渐增加，知道等于toOffset时停止。
     * @param fromOffset 起始的数据偏移量
     * @param toOffset	目标的数据偏移量
     */
    public void toDownload(String remotePath, String localPath, long fromOffset, long toOffset){
	transportType = 0;
	remote = remotePath;
	local = localPath;
	nowOffset = fromOffset;
	originalOffset = fromOffset;
	targetOffset = toOffset;
	try{
	    if (fromOffset > 0)
    		output =new DataOutputStream(new FileOutputStream(new File(local)));
	    else
    		output =new DataOutputStream(new FileOutputStream(new File(local), true));
	}catch(FileNotFoundException e){
	    transportType = -1;
	}
	if (debug) System.out.println("start = " + fromOffset + "to =" + toOffset);
    }

    /**
     * 设置文件传输的类别为‘上传’
     * 数据偏移量会从fromOffset开始，根据读取到的数据长度逐渐增加，知道等于toOffset时停止。
     * @param fromOffset 起始的数据偏移量
     * @param toOffset	目标的数据偏移量
     */
    public void toUpload(String remotePath, String localPath, long fromOffset, long toOffset){
	transportType = 1;
	remote = remotePath;
	local = localPath;
	nowOffset = fromOffset;
	originalOffset = fromOffset;
	targetOffset = toOffset;
	try{
    	    input = new DataInputStream(new FileInputStream(new File(local)));
	    //跳过输入文件中的前面部分
	    long n = toOffset - fromOffset;
	    while (n > 0) n -= input.skipBytes((int) n);
	}catch(Exception e){
	    transportType = -1;
	}
	if (debug) System.out.println("start = " + fromOffset + "to =" + toOffset);
    }

    private void sendIOCmd() throws IOException{
	String response;
	//发送‘启动文件传输’指令
	if (transportType == 0)
	    response = ftp.send("RETR " + remote);
	else
	    response = ftp.send("STOR " + remote);
	if (!response.startsWith("150 "))
	    throw new IOException("start file transport failed.");
    }

    private void whenPasv() throws IOException{
	//获取被动模式下，服务器指定的TCP主机和端口
	String str = ftp.send("PASV");
	if (!str.startsWith("227 "))
	    throw new IOException("unable to use PASV mode.");
	//解析
	String[] data = str.substring(str.indexOf('(') + 1, str.lastIndexOf(')')).split(",");
	String dataHost = data[0] + "." + data[1] + "." + data[2] + "." + data[3];
	int dataPort = Integer.valueOf(data[4]).intValue() * 256
		    + Integer.valueOf(data[5]).intValue();
	if (debug)
	    System.out.println(":: data host = " + dataHost
			    +"\n:: data port = " + dataPort);
	//取得数据流socket
	dataSocket = new Socket(dataHost, dataPort);
	sendIOCmd();
    }

    /**
     *
     * 处理PORT模式下的数据流建立问题。     *
     * @throws java.io.IOException 读取错误时抛出
     */
    private void whenPort() throws IOException{
        System.out.println("call IOCell.whenPort()");
	//监听本地的一个空闲端口
	ServerSocket listen = new ServerSocket(0);
	int localPort = listen.getLocalPort();
	if (localPort == -1)
	    throw new IOException("unable to listen on tcp.");
	InetAddress address = getInetAddress();
	byte[] a = address.getAddress();
    String ip = address.getHostAddress().replace(".", ",");
	//通知服务器切换PORT主动模式
    //listen = new ServerSocket(localPort);
	String str = ftp.send("PORT " + ip + "," +
                (localPort >> 8) + "," + (localPort & 0xff) );
	if (!str.startsWith("200 "))
	    throw new IOException("unable to use PORT mode.");
	//取得数据流socket
	String response;
	if (transportType == 0)
	    response = ftp.send("RETR " + remote, false);
	else
	    response = ftp.send("STOR " + remote, false);
    System.out.print("accepting...");
	dataSocket = listen.accept();
    System.out.println("OK!");
	if (!response.startsWith("150 "))
	    throw new IOException("start file transport failed.");
    }

    private InetAddress getInetAddress() throws UnknownHostException{
        InetAddress address = InetAddress.getLocalHost();
        Enumeration inets;
        try{
            Enumeration netInterfaces=NetworkInterface.getNetworkInterfaces();
            while(netInterfaces.hasMoreElements()){
                NetworkInterface ni=(NetworkInterface)netInterfaces.nextElement();
                inets=ni.getInetAddresses();
                while(inets.hasMoreElements()){
                    address =(InetAddress) inets.nextElement();;
                    System.out.println(ni.getName() + ":\t" + address.getHostAddress());
                    if( !address.isAnyLocalAddress() && !address.isLoopbackAddress()
                            && address.getHostAddress().indexOf(":") == -1){
                        System.out.println("本机的ip=" + address.getHostAddress());
                        return address;
                    }
                }
            }
        }catch(Exception e){
            System.out.println("IOCell.whenPort() " + e);
       }
        return InetAddress.getLocalHost();
    }

    /**
     * 1、发送传输指令，启动传输。
     * 2、调用setStream()设置数据流socket。
     * 单独将此设置作为一个函数的原因是，socket连接可能会被断开，则需重新设置。
     * @note 只允许进行tryCount次尝试。
     */
    private synchronized void connect() throws IOException{
	if (--tryCount < 0) return;
        if (ftp.isConnected() == false){
            if (debug) System.out.println("login to ftp.");
            error = false;
            if (ftp.connect() == false){
                error = true;
                return;
            }
        }
	//设置数据流为二进制模式
	ftp.bin();
	//发送‘文件偏移设置’指令
	String response = ftp.send("REST " + nowOffset);
	if (!response.startsWith("350 "))
	    throw new IOException("set file offset failed.\n" + response);
	//设置数据流模式
	int mode = ftp.getMode();
	if (mode == 1)	whenPasv();
	else		whenPort();
	if (dataSocket == null)
	    throw new IOException("Can not establish data socket.");

	if (debug) System.out.println(":: dataSocket prepared.");
	if (transportType == 0)
    	    input = new DataInputStream(dataSocket.getInputStream());
	else
    	    output =new DataOutputStream(dataSocket.getOutputStream());
    }

    byte[] buffer = new byte[4096];
    int bytesRead = 0;
    /**
     * 循环执行IO操作。
     */
    private synchronized void transport() throws IOException{
	if (error || finished || tryCount < 1) return;
        System.out.println("IOCell.transport()");
	//socket的关闭与输入流的读取错误都将导致函数返回。
	while (!dataSocket.isClosed()){
	    if (debug) System.out.print("reading... ");
            try{
                bytesRead = input.read(buffer);
                if (debug) System.out.println("offset = " +nowOffset +"\t\tbytesRead = " +bytesRead);
                if (bytesRead == -1){
                    if (nowOffset == targetOffset)
                        finish();
                    return;
                }
                if (timer == null){
                    timer = new Timer();
                    timer.schedule(new CheckSpeed(), 0, 1000);
                }
                output.write(buffer, 0, bytesRead);
                nowOffset += bytesRead;
            }catch (IOException e){
                if (debug) System.out.println("IOCell.transport() " + e);
            }
	}
    }

    private void finish() throws IOException{
	System.out.println(":: Transport.finish()");
	finished = true;
        try{
            timer.cancel();
            input.close();
        }catch (NullPointerException e){}
	output.flush();
	output.close();
	dataSocket.close();
	//String response = ftp.read();
	//if (debug)
	//    if (!response.startsWith("226 "))
	//	System.out.println("this thread is over. but total transfer is not compelte");
    if (ftp.isConnected()) ftp.disconnect();
	return;
    }

    public void ioStart(){
        tryCount = 10;
        finished = false;
        try{
            ftp.connect();
            //connect();
            //transport();
        }catch (IOException e){}
        run();//this.notify();
        start();
    }
    public void ioStop(){
        tryCount = 0;
        try{
            dataSocket.close();
            if (ftp.isConnected()){
                ftp.read();
                ftp.disconnect();
            }
            //ftp.disconnect();
        }catch(IOException e){
        }
    }
    public void ioDelete(){
        try{
            finish();
        }catch(IOException e){
        }
    }

    String title = "untitled";

    public long getReceivedSize(){
	return nowOffset - originalOffset;
    }

    public long getTotalSize(){
	return targetOffset - originalOffset;
    }

    public int getRate(){
	return 100*(int)(nowOffset - originalOffset)/ (int)(targetOffset - originalOffset);
    }
    public long getSpeed(){	return speed;    }
    public long getAverageSpeed(){	return averageSpeed;    }
    public long getCurrentSpeed(){	return currentSpeed;    }
    public String getTitle(){   return title;    }
    public CoreFtp getFtp(){    return ftp;}
    public boolean isFinished(){return finished;    }

    public void setAutoTry(int count){
	if (count > 1) tryCount = count;
    }

    Timer timer = null;
    long speed = 0;
    long averageSpeed = 0;
    long currentSpeed = 0;

    class CheckSpeed extends TimerTask{
	long total = 0;
	int n = 0;
	long last = 0;
	public CheckSpeed(){
	    total = targetOffset - originalOffset;
	}
	public void run(){
	    n ++;
	    long now = nowOffset - originalOffset;
	    speed = (int) (speed * 0.9 + (now - last) * 0.1) ;
	    averageSpeed = now / n;
	    currentSpeed = now - last;
	    last = now;
	}
    }
}
